﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Data;
using System.Reflection;
using System.Threading.Tasks;

using Nest;
using Elasticsearch.Net.Connection.Thrift;

namespace XMK.Framework.ElasticSearch
{
    public class ElasticSearchDataAccess
    {
        public ElasticSearchConnection Connection
        {
            get;
            private set;
        }

        public ElasticSearchDataAccess(ElasticSearchConnection conn)
        {
            if (conn == null)
            {
                throw new ArgumentNullException("conn 参数不能为空");
            }
            Connection = conn;
        }

        public ElasticSearchFields Fields
        {
            get;
            set;
        }

        public ElasticSearchOrder Order
        {
            get;
            set;
        }

        private List<ElasticSearchHighlight> _highlightList = new List<ElasticSearchHighlight>();
        public List<ElasticSearchHighlight> HighLightList
        {
            get
            {
                return _highlightList;
            }
            set
            {
                _highlightList = value;
            }
        }
        
        public IEnumerable<T> ExecuteQuery<T>(ElasticSearchQuery esQuery, string typeName
            , int pageIndex, int pageSize, ref long recordTotal)
            where T: class
        {
            if (string.IsNullOrWhiteSpace(typeName))
            {
                throw new ArgumentNullException("参数 typeName 不能为空");
            }
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings, new ThriftConnection(connectionSettings));
            }
            SearchDescriptor<dynamic> descriptor = new SearchDescriptor<dynamic>();

            if (esQuery == null || esQuery.QueryDescriptor == null)
            {
                descriptor.Query(t => t.MatchAll());
            }
            else
            {
                descriptor.Query(esQuery.QueryDescriptor);
            }
            if (Fields != null && Fields.Fields.Count() > 0)
            {
                descriptor.Fields(Fields.Fields.ToArray());
            }
            if (Order != null && Order.SortDict.Count() > 0)
            {
                foreach (var sort in Order.SortDict)
                {
                    if (sort.Value == ElasticSearchSortMode.Asc)
                    {
                        descriptor.SortAscending(sort.Key);
                    }
                    else
                    {
                        descriptor.SortDescending(sort.Key);
                    }
                } 
            }
            int from = (pageIndex - 1) * pageSize;
            var result = client.Search<dynamic,T>((d) => {
                d = descriptor;
                return d;
            });
            recordTotal = result.Total;
            return result.Documents;
        }

        public IEnumerable<T> ExecuteSuggest<T>(int pageIndex, int pageSize, ref long recordTotal) where T: class
        {
            
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings, new ThriftConnection(connectionSettings));
            }
            SearchDescriptor<dynamic> descriptor = new SearchDescriptor<dynamic>();

            descriptor.Query(t => t.MatchAll());

            if (Fields != null && Fields.Fields.Count() > 0)
            {
                descriptor.Fields(Fields.Fields.ToArray());
            }
            if (Order != null && Order.SortDict.Count() > 0)
            {
                foreach (var sort in Order.SortDict)
                {
                    if (sort.Value == ElasticSearchSortMode.Asc)
                    {
                        descriptor.SortAscending(sort.Key);
                    }
                    else
                    {
                        descriptor.SortDescending(sort.Key);
                    }
                }
            }
            int from = (pageIndex - 1) * pageSize;
            var result = client.Search<dynamic, T>((d) =>
            {
                d = descriptor;
                d.SuggestTerm("fILE_NAME_SUGGEST", 
                    m => m.SuggestMode(SuggestMode.Popular).Text("eth").Size(2).OnField("uSER_ID"));
                return d;
            });
            recordTotal = result.Total;
            return result.Documents;
        }

        public Dictionary<string,Dictionary<string, long>> ExecuteQueryFacet<T>(int pageIndex, int pageSize,ElasticSearchQuery esQuery, List<string> facetFieldList) where T : class
        {
            Dictionary<string, Dictionary<string, long>> dict = new Dictionary<string, Dictionary<string, long>>();
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings, new ThriftConnection(connectionSettings));
            }
            SearchDescriptor<dynamic> descriptor = new SearchDescriptor<dynamic>();

            if (Fields != null && Fields.Fields.Count() > 0)
            {
                descriptor.Fields(Fields.Fields.ToArray());
            }

            var queryResults = client.Search<T>((x) =>
            {
                SearchDescriptor<T> xx = x;
                xx.From(0)
                  .Size(10)
                  .Query(esQuery.QueryDescriptor);
                foreach (var item in facetFieldList)
                {
                    xx.FacetTerm(t => t.OnField(item));
                }
                return xx;
            });

            foreach (var item in facetFieldList)
            {
                var facetItems = queryResults.FacetItems<FacetItem>(item);
                Dictionary<string, long> d = new Dictionary<string, long>();
                foreach (var facet in facetItems)
                {
                    var termItem = facet as TermItem;
                    d.Add(termItem.Term, termItem.Count);
                }
                dict.Add(item, d);
            }

            return dict;
        }

        public QueryResponse<T> ExecuteQueryResponse<T>(ElasticSearchQuery esQuery, string typeName
            , int pageIndex, int pageSize, ref long recordTotal)
            where T : class
        {
            if (string.IsNullOrWhiteSpace(typeName))
            {
                throw new ArgumentNullException("参数 typeName 不能为空");
            }
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings, new ThriftConnection(connectionSettings));
            }
            SearchDescriptor<dynamic> descriptor = new SearchDescriptor<dynamic>();

            if (esQuery == null || esQuery.QueryDescriptor == null)
            {
                descriptor.Query(t => t.MatchAll());
            }
            else
            {
                descriptor.Query(esQuery.QueryDescriptor);
            }
            if (Fields != null && Fields.Fields.Count() > 0)
            {
                descriptor.Fields(Fields.Fields.ToArray());
            }
            if (Order != null && Order.SortDict.Count() > 0)
            {
                foreach (var sort in Order.SortDict)
                {
                    if (sort.Value == ElasticSearchSortMode.Asc)
                    {
                        descriptor.SortAscending(sort.Key);
                    }
                    else
                    {
                        descriptor.SortDescending(sort.Key);
                    }
                }
            }
            int from = (pageIndex - 1) * pageSize;
            var result = client.Search<dynamic, T>((d) =>
            {
                d = descriptor;
                if (_highlightList != null && _highlightList.Count() > 0)
                {
                    List<Action<HighlightFieldDescriptor<dynamic>>> highLights = new List<Action<HighlightFieldDescriptor<dynamic>>>();
                    foreach (var high in _highlightList)
                    {
                        var newHigh = high; //防止闭包
                        highLights.Add((h) =>
                            {
                                
                                h.PreTags(newHigh.PreTags).PostTags(newHigh.PostTags).OnField(newHigh.FieldName).BoundaryMaxSize(newHigh.MaxSize);
                            });
                    }
                    d.Highlight(h => h.OnFields(highLights.ToArray()));
                }
                return d.From(from).Take(pageSize).Index(Connection.Database).Type(typeName);
            });
            recordTotal = result.Total;
            return (QueryResponse<T>)result;
        }

        public QueryResponse<T> ExecuteFilterResponse<T>(ElasticSearchFilter esFilter, string typeName
            , int pageIndex, int pageSize, ref long recordTotal)
            where T : class, new()
        {
            if (string.IsNullOrWhiteSpace(typeName))
            {
                throw new ArgumentNullException("参数 typeName 不能为空");
            }
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings
                    , new ThriftConnection(connectionSettings));
            }

            SearchDescriptor<dynamic> descriptor = new SearchDescriptor<dynamic>();

            if (esFilter == null || esFilter.FilterDescriptor == null) {
                descriptor.Filter(t => t.MatchAll());
            }
            else {
                descriptor.Filter(esFilter.FilterDescriptor);
            }
            if (Fields != null && Fields.Fields.Count() > 0) {
                descriptor.Fields(Fields.Fields.ToArray());
            }
            if (Order != null && Order.SortDict.Count() > 0)
            {
                foreach (var sort in Order.SortDict)
                {
                    if (sort.Value == ElasticSearchSortMode.Asc)
                    {
                        descriptor.SortAscending(sort.Key);
                    }
                    else
                    {
                        descriptor.SortDescending(sort.Key);
                    }
                }
            }
            int from = (pageIndex - 1) * pageSize;
            var result = client.Search<dynamic,T>((d) => {
                d = descriptor;
                if (_highlightList != null && _highlightList.Count() > 0)
                {
                    List<Action<HighlightFieldDescriptor<dynamic>>> highLights = new List<Action<HighlightFieldDescriptor<dynamic>>>();
                    foreach (var high in _highlightList)
                    {
                        var newHigh = high;
                        highLights.Add((h) => h.PreTags(newHigh.PreTags).PostTags(newHigh.PostTags).OnField(newHigh.FieldName).BoundaryMaxSize(newHigh.MaxSize));
                    }
                    d.Highlight(h => h.OnFields(highLights.ToArray()));
                }
                return d.From(from).Take(pageSize).Index(Connection.Database).Type(typeName);
            });
            recordTotal = result.Total;
            return (QueryResponse<T>)result;
        }

        public IEnumerable<T> ExecuteFilter<T>(ElasticSearchFilter esFilter, string typeName
            , int pageIndex, int pageSize, ref long recordTotal)
            where T : class, new()
        {
            if (string.IsNullOrWhiteSpace(typeName))
            {
                throw new ArgumentNullException("参数 typeName 不能为空");
            }
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings
                    , new ThriftConnection(connectionSettings));
            }

            SearchDescriptor<dynamic> descriptor = new SearchDescriptor<dynamic>();

            if (esFilter == null || esFilter.FilterDescriptor == null)
            {
                descriptor.Filter(t => t.MatchAll());
            }
            else
            {
                descriptor.Filter(esFilter.FilterDescriptor);
            }
            if (Fields != null && Fields.Fields.Count() > 0)
            {
                descriptor.Fields(Fields.Fields.ToArray());
            }
            if (Order != null && Order.SortDict.Count() > 0)
            {
                foreach (var sort in Order.SortDict)
                {
                    if (sort.Value == ElasticSearchSortMode.Asc)
                    {
                        descriptor.SortAscending(sort.Key);
                    }
                    else
                    {
                        descriptor.SortDescending(sort.Key);
                    }
                }
            }
            int from = (pageIndex - 1) * pageSize;
            var result = client.Search<dynamic, T>((d) =>
            {
                d = descriptor;
                if (_highlightList != null && _highlightList.Count() > 0)
                {
                    List<Action<HighlightFieldDescriptor<dynamic>>> highLights = new List<Action<HighlightFieldDescriptor<dynamic>>>();
                    foreach (var high in _highlightList)
                    {
                        highLights.Add((h) => h.PreTags(high.PreTags).PostTags(high.PostTags).OnField(high.FieldName).BoundaryMaxSize(high.MaxSize));
                    }
                    d.Highlight(h => h.OnFields(highLights.ToArray()));
                }
                return d.From(from).Take(pageSize).Index(Connection.Database).Type(typeName);
            });
            recordTotal = result.Total;
            return result.Documents;
        }

        public void IndexMany<T>(IEnumerable<T> indexItemList, string index, string type)
            where T: class, new ()
        {
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings, new ThriftConnection(connectionSettings));
            }
            var indexResult = client.IndexMany<T>(indexItemList, index, type);
            
        }

        public Task IndexManyAsync<T>(IEnumerable<T> indexItemList, string index, string type)
            where T : class, new()
        {
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings, new ThriftConnection(connectionSettings));
            }
            return client.IndexManyAsync<T>(indexItemList, index, type);
        }

        public Task IndexAsync<T>(T model, string index, string type)
            where T : class, new()
        {
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings, new ThriftConnection(connectionSettings));
            }
            return client.IndexAsync<T>(model);
        }

        public bool Index<T>(T model, string index, string type)
            where T : class, new()
        {
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings, new ThriftConnection(connectionSettings));
            }
            var indexResult = client.Index<T>(model);
            return indexResult.OK;
        }

        public bool Delete(ElasticSearchQuery esQuery, string type)
        {
            if (string.IsNullOrWhiteSpace(type))
            {
                throw new ArgumentNullException("参数 type 不能为空");
            }
            var connectionSettings = new ConnectionSettings(new Uri(Connection.GetUri()), Connection.Database);
            ElasticClient client = new ElasticClient(connectionSettings);
            if (Connection.ConnType == ConnectionType.Thrift)
            {
                client = new ElasticClient(connectionSettings, new ThriftConnection(connectionSettings));
            }
            DeleteByQueryDescriptor<dynamic> descriptor = new DeleteByQueryDescriptor<dynamic>();

            if (esQuery == null || esQuery.QueryDescriptor == null)
                return false;
            else
            {
                descriptor.Query(f => esQuery.QueryDescriptor);
            }
            var result = client.DeleteByQuery<dynamic>(f => descriptor.Type(type));

            return result.IsValid;
        }
    }

    public static class IEnumerableExt
    {
        public static DataTable ModelToDataTable<T>(this IEnumerable<T> modelList)
        {
            DataTable table = new DataTable();

            Type type = typeof(T);

            PropertyInfo[] propertyInfoArr = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            
            foreach (var property in propertyInfoArr)
            {
                table.Columns.Add(property.Name, property.PropertyType);
            }

            foreach (var model in modelList)
            {
                DataRow row = table.NewRow();

                foreach (var property in propertyInfoArr)
                {
                    row[property.Name] = property.GetValue(model, null);
                }

                table.Rows.Add(row);
            }

            return table;
        }
    }
}
